/*
 *	Matthew Smith
 *	5/22/2010
 *	TCSS 360
 *
 *	Changelog:
 *	5/22/2011 - Created class with both constructors, add(), 
 *				getViolatedStudentPreferences(), and getStudents().
 *	5/26/2011 - Changed method calls in getViolatedStudentPrefs() to match
 *				APIs of Course and Student.
 * 	5/28/2011 - Added the getViolatedAdvisorPrefs() method.
 *  5/30/2011 - Fixed bug in getViolatedStudentPrefs() and added File parsing constructor.
 *  5/31/2011 - Added getTeachers() method for use by the Scheduler.
 *  5-31-2011 - (Michael) Fixed some bugs in parse method  added toString for test
 */

package main;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
 
 /**
  *	This class contains a list of all the DataUsers of the program.
  *	@author Matthew Smith
  *	@version 5/31/2011
  */
 public class UserList {
	
	/**
	 *	The list of all DataUsers.
	 */
	private List<DataUser> my_users;
	
	/**
	 *	Creates a UserList containing no DataUsers.
	 */
	public UserList() {
		my_users = new ArrayList<DataUser>();
	}
	
	/**
	 *	Creates a UserList containing the DataUsers of the_users.
	 *	@param the_users The DataUsers to add to the new UserList
	 */
	public UserList(final List<DataUser> the_users) {
		my_users = new ArrayList<DataUser>(the_users);
	}
	
	/**
	 * Creates a UserList from the data contained in the provided file.
	 * 
	 * pre: The File must be a CSV in the proper format.
	 * post: All users and associated data will be added to the UserList.
	 * 
	 * @throws FileNotFoundException 
	 */
	public UserList(final File the_file) throws FileNotFoundException {
		my_users = new ArrayList<DataUser>(parse(the_file));
	}
	
	/**
	 *	Adds the_user to the UserList.
	 *	@param the_user The DataUser to add to this UserList
	 */
	public void add(final DataUser the_user) {
		my_users.add(the_user);
	}

	/**
	 *	Returns a Map containing the Courses that violated a Students preference to how many 
	 *	Students who's preference was violated by that Course. A Course will violate a 
	 *	Student's preference if that Course occurs at a time that the Student cannot attend
	 *	or if the Student prefers a Course that is not offered by the schedule.
	 *
	 *	@param the_schedule The Course schedule to check.
	 *	@return A Map of Course violations to occurrences of that violation.
	 */
	public Map<Course, Integer> getViolatedStudentPreferences(final List<Course> the_schedule) {
		final List<Student> students = new ArrayList<Student>(getStudents());		
		final Map<Course, Integer> violations = new HashMap<Course, Integer>();
		
		for (Student student : students) {		
			final List<Course> student_courses = new ArrayList<Course>(student.getCoursePrefs());
			
			for (Course schedule_course : the_schedule) {
				int i = student_courses.indexOf(schedule_course);
				if (i >= 0) {
					boolean  within = false;
					for (TimeBlock time : student.getTimePrefs()) {
						within = (within | schedule_course.getTimeblock().isWithin(time));
					}
					if (within) {
						//preference is met, so remove from list
						student_courses.remove(i);	
					} 
				}
			}
			//these courses were preferred, but not offered at a preferred time
			//or not offered at all
			for (Course course : student_courses) {		
				if (violations.containsKey(course)) {	
					violations.put(course, violations.get(course) + 1);
				} else {
					violations.put(course, Integer.valueOf(1));
				}
			}
		}
		return violations;
	}
	
	/**
	 * Returns a Map containing Advisors mapped to a List of the Courses that they preferred but weren't
	 * offered in the_schedule. If every Course an Advisor preferred was in the_schedule, then this List
	 * will be empty.
	 * 
	 * pre: the_scheudle != null
	 * post: The returned Map will contain every Advisor in UserList as a key mapped to a List
	 * 	     of Courses. A Course will appear in the List if that Course was returned by the 
	 *       getCoursePrefs() method of Advisor and was not contained in the_schedule. This List
	 *       may be empty.
	 *       
	 * @param the_schedule The schedule of Courses in List form.      
	 * @return A Map of an Advisor to the Courses that violated their preference.
	 */
	public Map<DataUser, List<Course>> getViolatedAdvisorPreferences(final List<Course> the_schedule) {
		final Map<DataUser, List<Course>> violations = new HashMap<DataUser, List<Course>>();
		final List<DataUser> advisors = new ArrayList<DataUser>(getAdvisors());
		
		for (DataUser advisor : advisors) {
			final List<Course> violated_courses = new ArrayList<Course>();
			for (Course course : advisor.getCoursePrefs()) {
				if (!the_schedule.contains(course)) {
					violated_courses.add(course);
				}
			}
			violations.put(advisor, violated_courses);
		}
		
		return violations;
	}
	
	/**
	 * Returns a Map containing Teachers mapped to a List of the Courses that they did not prefer but were
     * assigned to teach. If no teachers were assign Courses that they didn't want, then this List
     * will be empty.
     * 
     * pre: the_scheudle != null
     * post: The returned Map will contain every Teachers from UserList as a key mapped to a List
     *       of Courses. A Course will appear in the List if that Course was not returned by the 
     *       getCoursePrefs() method of Teacher and they were assigned to teach it. This List
     *       may be empty.
     *       
     * @param the_schedule The schedule of Courses in List form.      
     * @return A Map of an Advisor to the Courses that violated their preference.
	 */
	public Map<DataUser, List<Course>> getViolatedTeacherPreferences(final List<Course> the_schedule) {
		final Map<DataUser, List<Course>> violations = new HashMap<DataUser, List<Course>>();
		final List<Teacher> teachers = new ArrayList<Teacher>(getTeachers());
		
		for (Teacher teacher : teachers) {
		  final List<Course> assigned_courses = new ArrayList<Course>();
		  final List<Course> violated_courses = new ArrayList<Course>();
		  final List<Course> prefered_courses = teacher.getCoursePrefs();
		  
		  for (Course course : the_schedule) {
		    if (course.getProf().equals(teacher.getName())) {
		      assigned_courses.add(course);
		    }
		  }
		  
		  final Iterator<Course> itr = assigned_courses.iterator();
		  while (itr.hasNext()) {
		    Course course = itr.next();
		    if (prefered_courses.contains(course)) {
		      final List<TimeBlock> time_prefs = teacher.getTimePrefs();
		      for (TimeBlock time : time_prefs) {
		        if (!course.getTimeblock().isWithin(time)) {
		          violated_courses.add(course);
		        }
		      }
		    } else {
		      violated_courses.add(course);
		    }
		  }
		  
		  violations.put(teacher, violated_courses);
		}
		
		return violations;
	}
	
	/**
	 * Returns an unmodifiable view of the Teachers of the UserList.
	 * 
	 * post: The returned List will contain every Teacher in the UserList,
	 *       this List can be empty if there are no teachers. The List is
	 *       unmodifiable.
	 * 
	 * @return An unmodifiable List of Teachers
	 */
	final public List<Teacher> getTeachers() {
		final List<Teacher> teachers = new ArrayList<Teacher>();
		
		for (int i = 0; i < my_users.size(); i++) {
			if (my_users.get(i) instanceof Teacher) {
				teachers.add((Teacher) my_users.get(i));
			}
		}
		
		return Collections.unmodifiableList(teachers);
	}
	
	/**
	 * Returns all the Students in my_users.
	 * @return A List of all the Students in my_users.
	 */
	private List<Student> getStudents() {
		final List<Student> students = new ArrayList<Student>();
	
		for (int i = 0; i < my_users.size(); i++) {
			if (my_users.get(i) instanceof Student) {
				students.add((Student) my_users.get(i));
			}
		}
		
		return students;
	}
	
	/**
	 * Returns all the Advisors in my_users.
	 * @return A List of all the Advisors in my_users.
	 */
	private List<DataUser> getAdvisors() {
		final List<DataUser> advisors = new ArrayList<DataUser>();
		
		for (int i = 0; i < my_users.size(); i++) {
			if (my_users.get(i) instanceof Advisor) {
				advisors.add(my_users.get(i));
			}
		}
		
		return advisors;
	}
	
	/**
	 * Parses a CSV file for the data needed to create users to add to the list.
	 * @throws FileNotFoundException 
	 */
	private List<DataUser> parse(final File the_file) throws FileNotFoundException {
		final List<DataUser> list = new ArrayList<DataUser>();
		
		final Scanner parser = new Scanner(the_file);
		while(parser.hasNextLine()) {
			String[] first_words = parser.nextLine().split(",");
			
			if (first_words[0].equals("student")) {
			  
				Student s = new Student(first_words[1], first_words[2]);
				
				
				String[] courses = parser.nextLine().split(",");
				final List<Course> prefs = new ArrayList<Course>();
				for (String course : courses) {
					prefs.add(new Course(course, "A", "", "", "MTWRF", "0000", "0001", 0));
				}
				s.setCoursePrefs(prefs);
				String[] times = parser.nextLine().split(",");
				final List<TimeBlock> time_prefs = new ArrayList<TimeBlock>();
				for (int i = 0; i < times.length; i += 3) {
					TimeBlock time = new TimeBlock(times[i], times[i + 1], times[i + 2]);
					time_prefs.add(time);
				}
				s.setTimePrefs(time_prefs);
				list.add(s);
			} else if (first_words[0].equals("teacher")) {
				Teacher t = new Teacher(first_words[1], first_words[2], first_words[3], Integer.parseInt(first_words[4].trim()));
				String[] courses = parser.nextLine().split(",");
				final List<Course> prefs = new ArrayList<Course>();
				for (String course : courses) {
				  prefs.add(new Course(course, "A", "", "", "MTWRF", "0000", "0001", 0));
                }
				t.setCoursePrefs(prefs);
				String[] times = parser.nextLine().split(",");
				final List<TimeBlock> time_prefs = new ArrayList<TimeBlock>();
				for (int i = 0; i < times.length; i += 3) {
					TimeBlock time = new TimeBlock(times[i], times[i + 1], times[i + 2]);
					time_prefs.add(time);
				}
				t.setTimePrefs(time_prefs);
				list.add(t);
			} else if (first_words[0].equals("advisor")) {
				Advisor a = new Advisor(first_words[1], first_words[2], first_words[3]);
				String[] courses = parser.nextLine().split(",");
				final List<Course> prefs = new ArrayList<Course>();
				for (String course : courses) {
				  prefs.add(new Course(course, "A", "", "", "MTWRF", "0000", "0001", 0));
                }
				a.setCoursePrefs(prefs);
				list.add(a);
			}
		}
		
		return list;
	}
	
	public String toString()
	{
	  StringBuilder sb = new StringBuilder();	  
	  for (DataUser du : my_users)
	  {
	    if(du instanceof Student)
	    sb.append(((Student) du).getUserName());
	    else if (du instanceof Teacher)
	      sb.append(((Teacher) du).getName());
	    else if (du instanceof Advisor)
	      sb.append(((Advisor)du).getUserName());
	    sb.append("\n");	    
	  }	  
	  return sb.toString();
	}
 }